package com.dana.system.service.exam.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.core.constants.Constants;
import com.core.enums.ResultCode;
import com.dana.security.exception.ServiceException;
import com.dana.system.domain.exam.ExamQuestion;
import com.dana.system.domain.exam.dto.ExamAddDTO;
import com.dana.system.domain.exam.dto.ExamAddQuestionDTO;
import com.dana.system.domain.exam.dto.ExamEditDTO;
import com.dana.system.domain.exam.dto.QueryExamDTO;
import com.dana.system.domain.exam.Exam;
import com.dana.system.domain.exam.vo.ExamDetailVO;
import com.dana.system.domain.exam.vo.ExamVO;
import com.dana.system.domain.question.Question;
import com.dana.system.domain.question.vo.QuestionVO;
import com.dana.system.manager.ExamCacheManager;
import com.dana.system.mapper.exam.ExamMapper;
import com.dana.system.mapper.exam.ExamQuestionMapper;
import com.dana.system.mapper.question.QuestionMapper;
import com.dana.system.service.exam.IExamService;
import com.github.pagehelper.PageHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion>
        implements IExamService {

    @Autowired
    private ExamMapper examMapper;
    @Autowired
    private QuestionMapper questionMapper;
    @Autowired
    private ExamQuestionMapper examQuestionMapper;
    @Autowired
    private ExamCacheManager examCacheManager;

    /**
     * 查询列表
     * @param queryExamDTO 参数
     */
    @Override
    public List<ExamVO> getList(QueryExamDTO queryExamDTO) {
        //mybatis自动分页
        PageHelper.startPage(queryExamDTO.getPageNum(), queryExamDTO.getPageSize());
        List<ExamVO> examVOS = examMapper.selectExamList(queryExamDTO);
        return examVOS;
    }


    /**
     * 新增竞赛,不带题目的
     *
     * @param examAddDTO 竞赛基本信息
     * @return 成功/失败
     */
    @Override
    public Long add(ExamAddDTO examAddDTO) {
        //校验当前添加的竞赛是否存在
        checkExamParams(examAddDTO, null);
        Exam exam = new Exam();
        BeanUtil.copyProperties(examAddDTO, exam);
        examMapper.insert(exam);
        return exam.getExamId();
    }



    /**
     * 在竞赛中添加题目
     * @param examAddQuestionDTO 题目信息
     */
    @Override
    public boolean addQuestion(ExamAddQuestionDTO examAddQuestionDTO) {
        //判断竞赛是否存在
        Exam exam = getExam(examAddQuestionDTO.getExamId());
        //校验竞赛是否开始
        checkExam(exam);
        //判断题目id集合是否为空,如果集合为空直接返回true
        Set<Long> questionIdSet = examAddQuestionDTO.getQuestionIdSet();
        if (CollectionUtil.isEmpty(questionIdSet)) {
            return true;
        }
        //从题目表中根据请求中的题目id去题目表中查询是否存在
        List<Question> questionList = questionMapper.selectBatchIds(questionIdSet);
        //如果传入的题目id比数据库中的题目多,就证明有传入的题目id在题目表中不存在
        if (CollectionUtil.isEmpty(questionList) || questionIdSet.size() > questionList.size()) {
            throw new ServiceException(ResultCode.QUESTION_NOT_EXISTS);
        }
        //批量插入
        return saveExamQuestion(questionIdSet, exam);
    }

    /**
     * 获取竞赛详情
     * @param examId
     * @return
     */
    @Override
    public ExamDetailVO detail(Long examId) {
        ExamDetailVO examDetailVO=new ExamDetailVO();
        //判断竞赛是否存在
        Exam exam = getExam(examId);
        BeanUtil.copyProperties(exam,examDetailVO);

        //根据竞赛id去查该竞赛下的题目id
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)
                .orderByAsc(ExamQuestion::getQuestionOrder)
                .eq(ExamQuestion::getExamId, examId));
        if (CollectionUtil.isEmpty(examQuestionList)){
            //题目id为空直接返回竞赛基本信息
            return examDetailVO;
        }
        //将题目id转换成一个集合
        List<Long> questionIdList = examQuestionList
                .stream()
                .map(ExamQuestion::getQuestionId)
                .toList();

        //根据题目id集合去题目表查询
        List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>()
                .select(Question::getQuestionId, Question::getDifficulty, Question::getTitle)
                .in(Question::getQuestionId, questionIdList));

        //将查询到的QuestionList转换为QuestionVOList
        List<QuestionVO> questionVOList = BeanUtil.copyToList(questionList, QuestionVO.class);
        //保存到examDetailVO中返回给前端
         examDetailVO.setExamQuestionList(questionVOList);
         return examDetailVO;
    }

    /**
     * 竞赛基本信息修改
     *
     * 必须要在竞赛撤销发布之后才可以进行修改和删除,
     * 这样就可以不用在修改和删除接口中维护redis了,只需要在撤销发布之后进行修改删除竞赛操作
     * 发布之后缓存到redis中,这样无论是修改还是删除竞赛接口就都可以不用额外维护redis
     * @param examEditDTO
     * @return
     */
    @Override
    public int edit(ExamEditDTO examEditDTO) {
        Exam exam = getExam(examEditDTO.getExamId());
        checkExam(exam);
        //参数校验,因为ExamEditDTO继承了ExamAddDTO,所以也可以使用
        checkExamParams(examEditDTO,examEditDTO.getExamId());
        //竞赛已经发布不允许操作
        if (Constants.TRUE.equals(exam.getStatus())){
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        //修改信息
        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());
        return examMapper.updateById(exam);
    }

    /**
     * 删除竞赛中题目
     * @param examId
     * @param questionId
     * @return
     */
    @Override
    public int delete(Long examId, Long questionId) {
        Exam exam = getExam(examId);
        checkExam(exam);
        //竞赛已经发布不允许操作
        if (Constants.TRUE.equals(exam.getStatus())){
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        return examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId,examId)
                .eq(ExamQuestion::getQuestionId,questionId)
        );
    }

    /**
     *删除竞赛
     * 必须要在竞赛撤销发布之后才可以进行修改和删除,
     *这样就可以不用在修改和删除接口中维护redis了,只需要在撤销发布之后进行修改删除竞赛操作
     *发布之后缓存到redis中,这样无论是修改还是删除竞赛接口就都可以不用额外维护redis
     * @param examId
     * @return
     */
    @Override
    public int deleteExam(Long examId) {
        //判断竞赛是否存在
        Exam exam = getExam(examId);
        //判断是否开赛
        checkExam(exam);
        if (Constants.TRUE.equals(exam.getStatus())){
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        //走到这就可以删除
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().eq(ExamQuestion::getExamId,examId));
        return examMapper.deleteById(examId);
    }

    /**
     * 发布竞赛
     * 关于竞赛中的题目来说,如果我们新增题目或者删除题目都需要先撤销发布
     * 我们就可以在竞赛撤销发布之后,先将redis中缓存的竞赛题目信息进行删除
     * 后续如果有添加或者删除题目的时候,当用户点击竞赛,我们就会重新去数据库中刷新redis缓存.
     * @see  //com.dana.friend.manager.ExamCacheManager.refreshExamQuestion
     * @param examId
     * @return
     */
    @Override
    public int publish(Long examId) {
        //判断竞赛是否存在
        Exam exam = getExam(examId);

        //判断当前竞赛是否已经完赛了
        if (exam.getEndTime().isBefore(LocalDateTime.now())){
            throw new ServiceException(ResultCode.EXAM_IS_FINISHED);
        }

        //判断竞赛是否包含题目
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        if (count==null||count<=0){
            throw new ServiceException(ResultCode.EXAM_NOT_HAVE_QUESTION);
        }
        exam.setStatus(Constants.TRUE);
        //将已经发布的竞赛缓存到redis中
        examCacheManager.addCache(exam);
        return examMapper.updateById(exam);
    }

    /**
     * 撤销发布竞赛
     * 关于竞赛中的题目来说,如果我们新增题目或者删除题目都需要先撤销发布
     *  我们就可以在竞赛撤销发布之后,先将redis中缓存的竞赛题目信息进行删除
     *  后续如果有添加或者删除题目的时候,当用户点击竞赛,我们就会重新去数据库中刷新redis缓存
     *  @see  //com.dana.friend.manager.ExamCacheManager.refreshExamQuestion
     * @param examId
     * @return
     */
    @Override
    public int cancelPublish(Long examId) {
        //判断竞赛是否存在
        Exam exam = getExam(examId);
        //判断当前竞赛是否已经完赛了
        if (exam.getEndTime().isBefore(LocalDateTime.now())){
            throw new ServiceException(ResultCode.EXAM_IS_FINISHED);
        }
        //是否已经开赛
        checkExam(exam);
        exam.setStatus(Constants.FALSE);
        //将取消发布的竞赛在redis中删除
        examCacheManager.deleteCache(examId);
        return examMapper.updateById(exam);
    }


    private static void checkExam(Exam exam) {
        if (exam.getStartTime().isBefore(LocalDateTime.now())){
            throw new ServiceException(ResultCode.EXAM_IS_START);
        }
    }


    private void checkExamParams(ExamAddDTO examSaveDTO,Long examId) {
        //竞赛标题不能重复
        LambdaQueryWrapper<Exam> eq = new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, examSaveDTO.getTitle())
                //ne就是排除自身,因为可能不会修改标题,所以可能会产生冲突才使用ne
                .ne(examId!=null ,Exam::getExamId,examId);
        List<Exam> examList = examMapper.selectList(eq);
        if (CollectionUtil.isNotEmpty(examList)){
            throw new ServiceException(ResultCode.EXAM_ALREADY_EXISTS);
        }

        //竞赛开始时间在当前时间之前
        if (examSaveDTO.getStartTime().isBefore(LocalDateTime.now())){
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);
        }
        //竞赛开始时间在结束时间之后
        if(examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())){
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
        }
    }


    private boolean saveExamQuestion(Set<Long> questionSet, Exam exam) {
        //创建一个集合,对竞赛和题目的关系表进行批量插入 竞赛id:题目id
        List<ExamQuestion> examQuestionList = new ArrayList<>();
        //题目顺序
        int num = 1;
        for (Long questionId : questionSet) {
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(num++);
            // examQuestionMapper.insert(examQuestion);
            examQuestionList.add(examQuestion);
        }
        //批量插入
        return saveBatch(examQuestionList);
    }

    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam==null){
           throw new ServiceException(ResultCode.EXAM_NOT_EXISTS);
        }
        return exam;
    }
}
